package com.michaelbulava.TFS.ui.controls.generic;

import com.microsoft.tfs.util.Check;

import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

/**
 * Created by mbulava on 6/15/2015.
 */
public abstract class CheckboxTable<T extends Object> extends JTable {
    private final CheckboxTableModel<T> model;
    protected static Border noFocusBorder =
            new EmptyBorder(1, 1, 1, 1);

    protected void CheckboxItemStateChanges(CheckboxItem<T> item){

    }

    private RowSorter<TableModel> sorter;

    public CheckboxTable(){
        super();
        setDefaultRenderer(CheckboxItem.class, new CheckboxCellRenderer());
        setRowSelectionAllowed(true);
        model = new CheckboxTableModel();
        setModel(model);
        setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        sorter = new TableRowSorter<TableModel>(getModel()){
            @Override
            public Comparator<?> getComparator(int column) {
                if (column == getCheckboxItemColumnIndex()){
                    return getSorterComparator();
                } else {
                    return super.getComparator(column);
                }
            }
        };
        setRowSorter(sorter);
        addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                if (e.getClickCount() == 2){
                    int i = CheckboxTable.this.rowAtPoint(e.getPoint());
                    List<? extends RowSorter.SortKey> keys = getRowSorter().getSortKeys();

                    int modelIndex = getRowSorter().convertRowIndexToModel(i);
                    CheckboxItem<T> checkboxItem = (CheckboxItem<T>) model.getValueAt(modelIndex, 0);
                    checkboxItem.setSelected(!checkboxItem.isSelected());

                    //getRowSorter().setSortKeys(null);
                    model.fireTableRowsUpdated(modelIndex, modelIndex);

//                    if (keys != null){
//                        getRowSorter().setSortKeys(keys);
//                    }
                    //getRowSorter().allRowsChanged();
                }

            }
        });
    }

    protected abstract Comparator<?> getSorterComparator();


    public void setData(T[] items){

        List<CheckboxItem<T>> list = new ArrayList<CheckboxItem<T>>();
        for(int i = 0; i < items.length; i++){
            final CheckboxItem<T> newItem = createNewCheckBoxItem(items[i]);
            newItem.setActionCommand(String.valueOf(i));
            newItem.addActionListener(new ActionListener(){

                @Override
                public void actionPerformed(ActionEvent e) {
                    newItem.setSelected(!newItem.isSelected());
                }

            });
            list.add(newItem);
        }
        model.setData(list.toArray(new CheckboxItem[0]));
    }

    public List<T> getAllListItems(){
        List<T> list = new ArrayList<T>();
        for(int i = 0; i < model.elements.length; i++){
            list.add(model.elements[i].getItem());
        }
        return list;
    }

    public List<T> getCheckedListItems(){
        List<T> list = new ArrayList<T>();
        if(model != null && model.elements != null){
            for(int i = 0; i < model.elements.length; i++){
                CheckboxItem<T> item =model.elements[i];
                if (item.isSelected())
                    list.add(item.getItem());
            }
        }

        return list;
    }

    public void setCheckedListItems(T[] items){
        //First clear all Checked items
        for (int j = 0; j < model.elements.length ; j++){
            if (model.elements[j].isSelected())
                model.elements[j].setSelected(false);
        }

        for(int i = 0; i < items.length; i++){
            T item = items[i];
            for (int j = 0; j < model.elements.length ; j++){
                T checkboxItem = model.elements[j].getItem();
                if (item.equals(checkboxItem)){
                    model.elements[j].setSelected(true);
                }
            }
        }
    }

    protected abstract CheckboxItem<T> createNewCheckBoxItem(T item);

    protected abstract Class getClassForColumn(int columnIndex);

    protected abstract Object getColumnValue(int columnIndex, T item);

    protected abstract String[] getColumnNames();

    protected abstract int getCheckboxItemColumnIndex();

    protected CheckboxItem<T> getCheckboxItem(T item){
        if (item == null)
            return null;

        if (model.elements != null){
            for(CheckboxItem<T> I : model.elements){
                if (I.getItem().equals(item))
                    return I;
            }
        }
        return null;
    }


    protected class CheckboxCellRenderer implements TableCellRenderer {

        @Override
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
            CheckboxItem<T> checkbox = (CheckboxItem<T>) value;
            checkbox.setBackground(isSelected ?
                    getSelectionBackground() : getBackground());
            checkbox.setForeground(isSelected ?
                    getSelectionForeground() : getForeground());
            checkbox.setEnabled(isEnabled());
            checkbox.setFont(getFont());
            checkbox.setFocusPainted(false);
            checkbox.setBorderPainted(true);
            checkbox.setBorder(isSelected ?
                    UIManager.getBorder(
                            "List.focusCellHighlightBorder") : noFocusBorder);
            return checkbox;
        }

    }

    protected abstract class CheckboxItem<U extends T> extends JCheckBox{
        protected final T item;

        public CheckboxItem(T item){
            super();
            Check.notNull(item, "item");
            this.item = item;
            this.setText(getTextForItem());
            this.addItemListener(new ItemListener() {
                @Override
                public void itemStateChanged(ItemEvent e) {
                    CheckboxItemStateChanges((CheckboxItem<T>) CheckboxItem.this);
                }
            });
//            Icon icon = getIconForItem();
//            if (icon != null)
//                this.setIcon(icon);
        }

        public T getItem(){
            return item;
        }

        protected abstract String getTextForItem();

        protected abstract Icon getIconForItem();
    }

    protected class CheckboxTableModel<V extends T> extends AbstractTableModel {
        private CheckboxItem<V>[] elements;
        private final String[] columnNames;
        private final int CheckboxColumnIndex;

        public CheckboxTableModel(){
            super();
            columnNames = getColumnNames();
            CheckboxColumnIndex = getCheckboxItemColumnIndex();
            Check.isTrue(CheckboxColumnIndex > -1, "Invalid CheckboxItem Column");
            Check.isTrue(CheckboxColumnIndex <= columnNames.length, "CheckboxItem column is outside of column size");
        }

        protected final void setData(CheckboxItem<V>[] items){
            int oldLen = getRowCount();
            elements = items;
//            if (elements != null && elements.length != oldLen)
            fireTableDataChanged();
        }

        @Override
        public final String getColumnName(int columnIndex){
            return columnNames[columnIndex];
        }

        @Override
        public final int getRowCount() {
            return elements == null ? 0 : elements.length;
        }

        @Override
        public final int getColumnCount() {
            return columnNames == null ? 0 : columnNames.length;
        }

        @Override
        public final Object getValueAt(int rowIndex, int columnIndex) {
            if(columnIndex == CheckboxColumnIndex)
                return elements[rowIndex];
            else
                return getColumnValue(columnIndex, elements[rowIndex].item);
        }

        @Override
        public final Class getColumnClass(int c) {
            if (c == CheckboxColumnIndex)
                return CheckboxItem.class;
            else
                return getClassForColumn(c);
        }
    }
}

